#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h> 
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#define TRUE 1
#define FALSE 0
#define FOOTER_SIZE 64
#define PATTERN 0xBEE4BEE4BEE4BEE4
#define DMA_SIZE 4*1024*1024
#define BUFFER_SIZE 32*1024*1024
#define VALID_DATA_SIZE (DMA_SIZE - FOOTER_SIZE)

typedef struct _res{
	int done[4];    //DMA DONE 
	char* buf[4];
	uint64_t data_len;
	char** argv;
	FILE* fp;
	int dev_id;
}res_type;

struct timeval start, end;
pthread_rwlock_t rwlock;

int res_init(res_type*);
int res_release(res_type*);
static void* file_op(res_type* res);
static void* device_op(res_type* res);

int main(int argc, char** argv)
{
    /*argc processing*/
    if (argc < 2){
        printf("Incorrect arguments\n");
        printf("\t%s <size in MB> <output file>\n", argv[0]);
        return 1;
    }
	
	res_type *res= (res_type *) malloc( sizeof(res_type));

	if(res == NULL){
		printf("res allocate failed!\n");
		return ENOMEM;
	}
	
	res->argv = argv;
	/*resource init*/
	res_init(res);

    /*pthread create*/
	pthread_t file_op_tid, device_op_tid;
	int file_op_rc=0, device_op_rc=0;
	device_op_rc = pthread_create(&device_op_tid, NULL, (void *)device_op, res);

	if(device_op_rc != 0)
		printf("%s: %d\n",__func__, strerror(device_op_rc));

	file_op_rc = pthread_create(&file_op_tid, NULL, (void *)file_op, res);

	if(file_op_rc != 0)
		printf("%s: %d\n",__func__, strerror(file_op_rc));

	gettimeofday(&start,NULL);

	/*wait for file_op thread done*/
	pthread_join(file_op_tid, NULL);
	gettimeofday(&end,NULL);

    float timeuse=1000000*(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec); /* in usec*/
    float bw = res->data_len/timeuse*1000000/1024.0/1024.0; 
    printf("%d MB data captured.\n",(res->data_len)>>20);
    printf("******Performace is : %lf MBps******\n",bw);
	
	/*resource release*/
	res_release(res);

	exit(0);	

}

int res_init(res_type* res){

    /*get data_len*/
    char *endptr = NULL;
    uint64_t size = strtol(res->argv[1], &endptr, 10);
	if (endptr == res->argv[1] || size < 1) {
		printf("Please input a valid integral number (base 10)\n");
		exit(0);
	}
	res->data_len = size*1024ll*1024ll;

	/*open file*/
    res->fp = fopen(res->argv[2],"wb");

	if (res->fp == NULL){
	    perror("Error_opening file");
		return 2;
    }

    /*memory allocate*/
	res->buf[0] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	res->buf[1] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	res->buf[2] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);
	res->buf[3] = (char *)malloc(BUFFER_SIZE + DMA_SIZE);

	/*uint64_t *footer = (char *) malloc(FOOTER_SIZE);*/
	//if ((res->buf[1] == NULL) || (res->buf[0] == NULL)) {
	if ((res->buf[1] == NULL) || (res->buf[0] == NULL) || (res->buf[2] == NULL) || (res->buf[3] == NULL)) {
		printf("Could not allocate buffer memory\n");
        return 3;
	}
    /*open pcie device*/
	res->dev_id = open("/dev/xbmd", O_RDWR);
   	if (res->dev_id <= 0) {
   		perror("Error opening xbmd device");
   		goto deverr;
   	}
    /*init rwlock*/
    pthread_rwlock_init(&rwlock, NULL);
	/*init status*/
	pthread_rwlock_wrlock(&rwlock);
	res->done[0] = FALSE;
	res->done[1] = FALSE;
	res->done[2] = FALSE;
	res->done[3] = FALSE;
	pthread_rwlock_unlock(&rwlock);
	return 0;

deverr:
	free(res->buf[0]);
	free(res->buf[1]);
	free(res->buf[2]);
	free(res->buf[3]);
}

int res_release(res_type* res){

	/*release memory*/
	free(res->buf[0]);
	free(res->buf[1]);
	free(res->buf[2]);
	free(res->buf[3]);

	/*close file and device*/
    fclose(res->fp);
	close(res->dev_id);
    /*diesotry rwlock*/
    pthread_rwlock_destroy(&rwlock);
	free(res);
	return 0;
}

/*
 *    */
static void* file_op(res_type *res)
{
	long file_len = res->data_len;
	long offset = 0;
	int dma_session_size;
	int i = 0;
    
	while( file_len > 0 ){
		if(file_len >= BUFFER_SIZE)
			dma_session_size = BUFFER_SIZE;
		else
			dma_session_size =  file_len;

        pthread_rwlock_rdlock(&rwlock); 
        
        if(res->done[i & 0x3] == TRUE){
			//printf("file_op:%d:file_len %llxMB\n",i,file_len>>20);
		    pthread_rwlock_unlock(&rwlock);
/*
            printf("res->done[0]:%d\n",i & 0x3,res->done[0]);
            printf("res->done[1]:%d\n",i & 0x3,res->done[1]);
            printf("res->done[2]:%d\n",i & 0x3,res->done[2]);
            printf("res->done[3]:%d\n",i & 0x3,res->done[3]);
*/
		    /*read data from pcie device and store as a file*/
		    fwrite(res->buf[i & 0x3], 1,dma_session_size,res->fp);

            pthread_rwlock_wrlock(&rwlock);
            res->done[i & 0x3] = FALSE; 
		    pthread_rwlock_unlock(&rwlock);

		    offset += dma_session_size ;
		    file_len -= dma_session_size;
		    i++;
        }else
		    pthread_rwlock_unlock(&rwlock);
        
	}
	pthread_exit(0);	
}

/*
 *    */
static void* device_op(res_type *res)
{

	uint64_t file_len = res->data_len;
	uint64_t offset=0;
	int dma_session_size;

	int i=0;
	while(file_len > 0 ){
	//		printf("device_op:%d:file_len %llxMB\n",i,file_len>>20);
			if(file_len >= BUFFER_SIZE)
				dma_session_size = BUFFER_SIZE;
			else
				dma_session_size = file_len;

            pthread_rwlock_rdlock(&rwlock);
            if(res->done[i & 0x3] == FALSE){
                
                pthread_rwlock_unlock(&rwlock);

    			lseek(res->dev_id,offset, SEEK_SET);
	    		read(res->dev_id, res->buf[i & 0x3], dma_session_size);

                pthread_rwlock_wrlock(&rwlock);
                res->done[i & 0x3]= TRUE;
/*
            printf("device_op:res->done[0]:%d\n",i & 0x3,res->done[0]);
            printf("device_op:res->done[1]:%d\n",i & 0x3,res->done[1]);
            printf("device_op:res->done[2]:%d\n",i & 0x3,res->done[2]);
            printf("device_op:ires->done[3]:%d\n",i & 0x3,res->done[3]);
*/
                pthread_rwlock_unlock(&rwlock);

			    offset += dma_session_size;
			    file_len -= dma_session_size;
			    i++; 
            }else
		        pthread_rwlock_unlock(&rwlock);

	}	

	pthread_exit(0);
}
